﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace LightPersist
{
	internal class WeakCache<T> where T : class
	{
		// protected to allow reflection and disable some optimizations
		readonly protected Dictionary<Guid, WeakReference> cache = new Dictionary<Guid, WeakReference>();

		public void Clear()
		{
			cache.Clear();
		}

		readonly public CacheSettings CacheSettings = new CacheSettings();

		public void CollectDiedEntries()
		{
			if (CacheSettings.ClearDiedEntries == CacheSettings.ClearDiedEntriesOn.Manually)
			{
				CollectDiedEntriesCore();
			}
			else
			{
				Trace.WriteLine("Manually Collect Died Entries: ClearDiedEntries != CacheSettings.ClearDiedEntriesOn.Manually", "Warning");
			}
		}

		void CollectDiedEntriesCore()
		{
			foreach (var key in (from kvp in cache
								 where !kvp.Value.IsAlive
								 select kvp.Key).ToArray())
			{
				cache.Remove(key);
			}
		}

		public void Remove(Guid guid)
		{
			cache.Remove(guid);
		}

		public T this[Guid id]
		{
// very dangerous
//			get
//			{
//				return GetValue(id, delegate
//				{
//					throw new Exception("get_Item[{0}]: Object does not cached".Arg(id.ToStringDebug()));
//				});
//			}
			set
			{
				if (value == null)
				{
					throw new ArgumentNullException("value");
				}
				cache[id] = new WeakReference(value);
			}
		}

		public T TryGetValue(Guid guid)
		{
			WeakReference wr;
			if(cache.TryGetValue(guid, out wr))
			{
				if (wr != null)
				{
					return (T) wr.Target;
				}
			}
			return null;
		}

		public T GetValue(Guid guid, Func<T> creator)
		{
			PerformAutoCollectIfNeed();

			WeakReference wref;
			if (!cache.TryGetValue(guid, out wref) || !wref.IsAlive)
			{
				var obj = creator();
#if DEBUG
				if (obj is ModelBase)
				{
					Debug.Assert(((ModelBase)(object)obj).ID == guid);
				}
#endif

				wref = new WeakReference(obj);
				cache[guid] = wref;
			}
			Debug.AssertExists(wref.Target);
			return (T)wref.Target;
		}

		int _autoCollectSeed;
		private void PerformAutoCollectIfNeed()
		{
			if (CacheSettings.ClearDiedEntries == CacheSettings.ClearDiedEntriesOn.Auto)
			{
				if (unchecked(++_autoCollectSeed) % CacheSettings.ClearDiedEntriesEveryNGetValue == 0)
				{
					CollectDiedEntriesCore();
				}
			}
		}
	}

	#region CacheSettings class

	internal class CacheSettings
	{
		public enum ClearDiedEntriesOn
		{
			Auto,
			Manually,
			Never,
		}

		public ClearDiedEntriesOn ClearDiedEntries = ClearDiedEntriesOn.Auto;
		public int ClearDiedEntriesEveryNGetValue = 512;
	}

	#endregion

	#region ModelBaseWeakCache class

	internal class ModelBaseWeakCache : WeakCache<ModelBase>
	{
	}

	#endregion
}